(function(global) {
    var LiteGraph = global.LiteGraph;

	var view_matrix = new Float32Array(16);
	var projection_matrix = new Float32Array(16);
	var viewprojection_matrix = new Float32Array(16);
	var model_matrix = new Float32Array(16);
	var global_uniforms = {
		u_view: view_matrix,
		u_projection: projection_matrix,
		u_viewprojection: viewprojection_matrix,
		u_model: model_matrix 
	};

	LiteGraph.LGraphRender = {
		onRequestCameraMatrices: null //overwrite with your 3D engine specifics, it will receive (view_matrix, projection_matrix,viewprojection_matrix) and must be filled
	};

	function generateGeometryId() {
		return (Math.random() * 100000)|0;
	}

	function LGraphPoints3D() {

		this.addInput("obj", "");
		this.addInput("radius", "number");

		this.addOutput("out", "geometry");
		this.addOutput("points", "[vec3]");
		this.properties = {
			radius: 1,
			num_points: 4096,
			generate_normals: true,
			regular: false,
			mode: LGraphPoints3D.SPHERE,
			force_update: false
		};

		this.points = new Float32Array( this.properties.num_points * 3 );
		this.normals = new Float32Array( this.properties.num_points * 3 );
		this.must_update = true;
		this.version = 0;

		var that = this;
		this.addWidget("button","update",null, function(){ that.must_update = true; });

		this.geometry = {
			vertices: null,
			_id: generateGeometryId()
		}

		this._old_obj = null;
		this._last_radius = null;
	}

	global.LGraphPoints3D = LGraphPoints3D;

	LGraphPoints3D.RECTANGLE = 1;
	LGraphPoints3D.CIRCLE = 2;

	LGraphPoints3D.CUBE = 10;
	LGraphPoints3D.SPHERE = 11;
	LGraphPoints3D.HEMISPHERE = 12;
	LGraphPoints3D.INSIDE_SPHERE = 13;

	LGraphPoints3D.OBJECT = 20;
	LGraphPoints3D.OBJECT_UNIFORMLY = 21;
	LGraphPoints3D.OBJECT_INSIDE = 22;

	LGraphPoints3D.MODE_VALUES = { "rectangle":LGraphPoints3D.RECTANGLE, "circle":LGraphPoints3D.CIRCLE, "cube":LGraphPoints3D.CUBE, "sphere":LGraphPoints3D.SPHERE, "hemisphere":LGraphPoints3D.HEMISPHERE, "inside_sphere":LGraphPoints3D.INSIDE_SPHERE, "object":LGraphPoints3D.OBJECT, "object_uniformly":LGraphPoints3D.OBJECT_UNIFORMLY, "object_inside":LGraphPoints3D.OBJECT_INSIDE };

	LGraphPoints3D.widgets_info = {
		mode: { widget: "combo", values: LGraphPoints3D.MODE_VALUES }
	};

	LGraphPoints3D.title = "list of points";
	LGraphPoints3D.desc = "returns an array of points";

	LGraphPoints3D.prototype.onPropertyChanged = function(name,value)
	{
		this.must_update = true;
	}

	LGraphPoints3D.prototype.onExecute = function() {

		var obj = this.getInputData(0);
		if( obj != this._old_obj || (obj && obj._version != this._old_obj_version) )
		{
			this._old_obj = obj;
			this.must_update = true;
		}

		var radius = this.getInputData(1);
		if(radius == null)
			radius = this.properties.radius;
		if( this._last_radius != radius )
		{
			this._last_radius = radius;
			this.must_update = true;
		}

		if(this.must_update || this.properties.force_update )
		{
			this.must_update = false;
			this.updatePoints();
		}

		this.geometry.vertices = this.points;
		this.geometry.normals = this.normals;
		this.geometry._version = this.version;

		this.setOutputData( 0, this.geometry );
	}

	LGraphPoints3D.prototype.updatePoints = function() {
		var num_points = this.properties.num_points|0;
		if(num_points < 1)
			num_points = 1;

		if(!this.points || this.points.length != num_points * 3)
			this.points = new Float32Array( num_points * 3 );

		if(this.properties.generate_normals)
		{
			if (!this.normals || this.normals.length != this.points.length)
				this.normals = new Float32Array( this.points.length );
		}
		else
			this.normals = null;

		var radius = this._last_radius || this.properties.radius;
		var mode = this.properties.mode;

		var obj = this.getInputData(0);
		this._old_obj_version = obj ? obj._version : null;

		this.points = LGraphPoints3D.generatePoints( radius, num_points, mode, this.points, this.normals, this.properties.regular, obj );

		this.version++;
	}

	//global
	LGraphPoints3D.generatePoints = function( radius, num_points, mode, points, normals, regular, obj )
	{
		var size = num_points * 3;
		if(!points || points.length != size)
			points = new Float32Array( size );
		var temp = new Float32Array(3);
		var UP = new Float32Array([0,1,0]);

		if(regular)
		{
			if( mode == LGraphPoints3D.RECTANGLE)
			{
				var side = Math.floor(Math.sqrt(num_points));
				for(var i = 0; i < side; ++i)
				for(var j = 0; j < side; ++j)
				{
					var pos = i*3 + j*3*side;
					points[pos] = ((i/side) - 0.5) * radius * 2;
					points[pos+1] = 0;
					points[pos+2] = ((j/side) - 0.5) * radius * 2;
				}
				points = new Float32Array( points.subarray(0,side*side*3) );
				if(normals)
				{
					for(var i = 0; i < normals.length; i+=3)
						normals.set(UP, i);
				}
			}
			else if( mode == LGraphPoints3D.SPHERE)
			{
				var side = Math.floor(Math.sqrt(num_points));
				for(var i = 0; i < side; ++i)
				for(var j = 0; j < side; ++j)
				{
					var pos = i*3 + j*3*side;
					polarToCartesian( temp, (i/side) * 2 * Math.PI, ((j/side) - 0.5) * 2 * Math.PI, radius );
					points[pos] = temp[0];
					points[pos+1] = temp[1];
					points[pos+2] = temp[2];
				}
				points = new Float32Array( points.subarray(0,side*side*3) );
				if(normals)
					LGraphPoints3D.generateSphericalNormals( points, normals );
			}
			else if( mode == LGraphPoints3D.CIRCLE)
			{
				for(var i = 0; i < size; i+=3)
				{
					var angle = 2 * Math.PI * (i/size);
					points[i] = Math.cos( angle ) * radius;
					points[i+1] = 0;
					points[i+2] = Math.sin( angle ) * radius;
				}
				if(normals)
				{
					for(var i = 0; i < normals.length; i+=3)
						normals.set(UP, i);
				}
			}
		}
		else //non regular
		{
			if( mode == LGraphPoints3D.RECTANGLE)
			{
				for(var i = 0; i < size; i+=3)
				{
					points[i] = (Math.random() - 0.5) * radius * 2;
					points[i+1] = 0;
					points[i+2] = (Math.random() - 0.5) * radius * 2;
				}
				if(normals)
				{
					for(var i = 0; i < normals.length; i+=3)
						normals.set(UP, i);
				}
			}
			else if( mode == LGraphPoints3D.CUBE)
			{
				for(var i = 0; i < size; i+=3)
				{
					points[i] = (Math.random() - 0.5) * radius * 2;
					points[i+1] = (Math.random() - 0.5) * radius * 2;
					points[i+2] = (Math.random() - 0.5) * radius * 2;
				}
				if(normals)
				{
					for(var i = 0; i < normals.length; i+=3)
						normals.set(UP, i);
				}
			}
			else if( mode == LGraphPoints3D.SPHERE)
			{
				LGraphPoints3D.generateSphere( points, size, radius );
				if(normals)
					LGraphPoints3D.generateSphericalNormals( points, normals );
			}
			else if( mode == LGraphPoints3D.HEMISPHERE)
			{
				LGraphPoints3D.generateHemisphere( points, size, radius );
				if(normals)
					LGraphPoints3D.generateSphericalNormals( points, normals );
			}
			else if( mode == LGraphPoints3D.CIRCLE)
			{
				LGraphPoints3D.generateInsideCircle( points, size, radius );
				if(normals)
					LGraphPoints3D.generateSphericalNormals( points, normals );
			}
			else if( mode == LGraphPoints3D.INSIDE_SPHERE)
			{
				LGraphPoints3D.generateInsideSphere( points, size, radius );
				if(normals)
					LGraphPoints3D.generateSphericalNormals( points, normals );
			}
			else if( mode == LGraphPoints3D.OBJECT)
			{
				LGraphPoints3D.generateFromObject( points, normals, size, obj, false );
			}
			else if( mode == LGraphPoints3D.OBJECT_UNIFORMLY)
			{
				LGraphPoints3D.generateFromObject( points, normals, size, obj, true );
			}
			else if( mode == LGraphPoints3D.OBJECT_INSIDE)
			{
				LGraphPoints3D.generateFromInsideObject( points, size, obj );
				//if(normals)
				//	LGraphPoints3D.generateSphericalNormals( points, normals );
			}
			else
				console.warn("wrong mode in LGraphPoints3D");
		}

		return points;
	}

	LGraphPoints3D.generateSphericalNormals = function(points, normals)
	{
		var temp = new Float32Array(3);
		for(var i = 0; i < normals.length; i+=3)
		{
			temp[0] = points[i];
			temp[1] = points[i+1];
			temp[2] = points[i+2];
			vec3.normalize(temp,temp);
			normals.set(temp,i);
		}
	}

	LGraphPoints3D.generateSphere = function (points, size, radius)
	{
		for(var i = 0; i < size; i+=3)
		{
			var r1 = Math.random();
			var r2 = Math.random();
			var x = 2 * Math.cos( 2 * Math.PI * r1 ) * Math.sqrt( r2 * (1-r2) );
			var y = 1 - 2 * r2;
			var z = 2 * Math.sin( 2 * Math.PI * r1 ) * Math.sqrt( r2 * (1-r2) );
			points[i] = x * radius;
			points[i+1] = y * radius;
			points[i+2] = z * radius;
		}			
	}

	LGraphPoints3D.generateHemisphere = function (points, size, radius)
	{
		for(var i = 0; i < size; i+=3)
		{
			var r1 = Math.random();
			var r2 = Math.random();
			var x = Math.cos( 2 * Math.PI * r1 ) * Math.sqrt(1 - r2*r2 );
			var y = r2;
			var z = Math.sin( 2 * Math.PI * r1 ) * Math.sqrt(1 - r2*r2 );
			points[i] = x * radius;
			points[i+1] = y * radius;
			points[i+2] = z * radius;
		}
	}

	LGraphPoints3D.generateInsideCircle = function (points, size, radius)
	{
		for(var i = 0; i < size; i+=3)
		{
			var r1 = Math.random();
			var r2 = Math.random();
			var x = Math.cos( 2 * Math.PI * r1 ) * Math.sqrt(1 - r2*r2 );
			var y = r2;
			var z = Math.sin( 2 * Math.PI * r1 ) * Math.sqrt(1 - r2*r2 );
			points[i] = x * radius;
			points[i+1] = 0;
			points[i+2] = z * radius;
		}
	}

	LGraphPoints3D.generateInsideSphere = function (points, size, radius)
	{
		for(var i = 0; i < size; i+=3)
		{
			var u = Math.random();
			var v = Math.random();
			var theta = u * 2.0 * Math.PI;
			var phi = Math.acos(2.0 * v - 1.0);
			var r = Math.cbrt(Math.random()) * radius;
			var sinTheta = Math.sin(theta);
			var cosTheta = Math.cos(theta);
			var sinPhi = Math.sin(phi);
			var cosPhi = Math.cos(phi);
			points[i] = r * sinPhi * cosTheta;
			points[i+1] = r * sinPhi * sinTheta;
			points[i+2] = r * cosPhi;
		}	
	}

	function findRandomTriangle( areas, f )
	{
		var l = areas.length;
		var imin = 0;
		var imid = 0;
		var imax = l;

		if(l == 0)
			return -1;
		if(l == 1)
			return 0;
		//dichotimic search
		while (imax >= imin)
		{
			imid = ((imax + imin)*0.5)|0;
			var t = areas[ imid ];
			if( t == f )
				return imid; 
			if( imin == (imax - 1) )
				return imin;
			if (t < f)
				imin = imid;
			else         
				imax = imid;
		}
		return imid;		
	}

	LGraphPoints3D.generateFromObject = function( points, normals, size, obj, evenly )
	{
		if(!obj)
			return;

		var vertices = null;
		var mesh_normals = null;
		var indices = null;
		var areas = null;
		if( obj.constructor === GL.Mesh )
		{
			vertices = obj.vertexBuffers.vertices.data;
			mesh_normals = obj.vertexBuffers.normals ? obj.vertexBuffers.normals.data : null;
			indices = obj.indexBuffers.indices ? obj.indexBuffers.indices.data : null;
			if(!indices)
				indices = obj.indexBuffers.triangles ? obj.indexBuffers.triangles.data : null;
		}
		if(!vertices)
			return null;
		var num_triangles = indices ? indices.length / 3 : vertices.length / (3*3);
		var total_area = 0; //sum of areas of all triangles

		if(evenly)
		{
			areas = new Float32Array(num_triangles); //accum
			for(var i = 0; i < num_triangles; ++i)
			{
				if(indices)
				{
					a = indices[i*3]*3;
					b = indices[i*3+1]*3;
					c = indices[i*3+2]*3;
				}
				else
				{
					a = i*9;
					b = i*9+3;
					c = i*9+6;
				}
				var P1 = vertices.subarray(a,a+3);
				var P2 = vertices.subarray(b,b+3);
				var P3 = vertices.subarray(c,c+3);
				var aL = vec3.distance( P1, P2 );
				var bL = vec3.distance( P2, P3 );
				var cL = vec3.distance( P3, P1 );
				var s = (aL + bL+ cL) / 2;
				total_area += Math.sqrt(s * (s - aL) * (s - bL) * (s - cL));
				areas[i] = total_area;
			}			
			for(var i = 0; i < num_triangles; ++i) //normalize
				areas[i] /= total_area;
		}

		for(var i = 0; i < size; i+=3)
		{
			var r = Math.random();
			var index = evenly ? findRandomTriangle( areas, r ) : Math.floor(r * num_triangles );
			//get random triangle
			var a = 0;
			var b = 0;
			var c = 0;
			if(indices)
			{
				a = indices[index*3]*3;
				b = indices[index*3+1]*3;
				c = indices[index*3+2]*3;
			}
			else
			{
				a = index*9;
				b = index*9+3;
				c = index*9+6;
			}
			var s = Math.random();
			var t = Math.random();
			var sqrt_s = Math.sqrt(s);
			var af = 1 - sqrt_s;
			var bf = sqrt_s * ( 1 - t);
			var cf = t * sqrt_s;
			points[i] = af * vertices[a] + bf*vertices[b] + cf*vertices[c];
			points[i+1] = af * vertices[a+1] + bf*vertices[b+1] + cf*vertices[c+1];
			points[i+2] = af * vertices[a+2] + bf*vertices[b+2] + cf*vertices[c+2];
			if(normals && mesh_normals)
			{
				normals[i] = af * mesh_normals[a] + bf*mesh_normals[b] + cf*mesh_normals[c];
				normals[i+1] = af * mesh_normals[a+1] + bf*mesh_normals[b+1] + cf*mesh_normals[c+1];
				normals[i+2] = af * mesh_normals[a+2] + bf*mesh_normals[b+2] + cf*mesh_normals[c+2];
				var N = normals.subarray(i,i+3);
				vec3.normalize(N,N);
			}
		}
	}

	LGraphPoints3D.generateFromInsideObject = function( points, size, mesh )
	{
		if(!mesh || mesh.constructor !== GL.Mesh)
			return;

		var aabb = mesh.getBoundingBox();
		if(!mesh.octree)
			mesh.octree = new GL.Octree( mesh );
		var octree = mesh.octree;
		var origin = vec3.create();
		var direction = vec3.fromValues(1,0,0);
		var temp = vec3.create();
		var i = 0;
		var tries = 0;
		while(i < size && tries < points.length * 10) //limit to avoid problems
		{
			tries += 1
			var r = vec3.random(temp); //random point inside the aabb
			r[0] = (r[0] * 2 - 1) * aabb[3] + aabb[0];
			r[1] = (r[1] * 2 - 1) * aabb[4] + aabb[1];
			r[2] = (r[2] * 2 - 1) * aabb[5] + aabb[2];
			origin.set(r);
			var hit = octree.testRay( origin, direction, 0, 10000, true, GL.Octree.ALL );
			if(!hit || hit.length % 2 == 0) //not inside
				continue;
			points.set( r, i );
			i+=3;
		}
	}

	LiteGraph.registerNodeType( "geometry/points3D", LGraphPoints3D );



	function LGraphPointsToInstances() {
		this.addInput("points", "geometry");
		this.addOutput("instances", "[mat4]");
		this.properties = {
			mode: 1,
			autoupdate: true
		};

		this.must_update = true;
		this.matrices = [];
		this.first_time = true;
	}

	LGraphPointsToInstances.NORMAL = 0;
	LGraphPointsToInstances.VERTICAL = 1;
	LGraphPointsToInstances.SPHERICAL = 2;
	LGraphPointsToInstances.RANDOM = 3;
	LGraphPointsToInstances.RANDOM_VERTICAL = 4;

	LGraphPointsToInstances.modes = {"normal":0,"vertical":1,"spherical":2,"random":3,"random_vertical":4};
	LGraphPointsToInstances.widgets_info = {
		mode: { widget: "combo", values: LGraphPointsToInstances.modes }
	};

	LGraphPointsToInstances.title = "points to inst";

	LGraphPointsToInstances.prototype.onExecute = function()
	{
		var geo = this.getInputData(0);
		if( !geo )
		{
			this.setOutputData(0,null);
			return;
		}

		if( !this.isOutputConnected(0) )
			return;

		var has_changed = (geo._version != this._version || geo._id != this._geometry_id);

		if( has_changed && this.properties.autoupdate || this.first_time )
		{
			this.first_time = false;
			this.updateInstances( geo );
		}

		this.setOutputData( 0, this.matrices );
	}

	LGraphPointsToInstances.prototype.updateInstances = function( geometry )
	{
		var vertices = geometry.vertices;
		if(!vertices)
			return null;
		var normals = geometry.normals;

		var matrices = this.matrices;
		var num_points = vertices.length / 3;
		if( matrices.length != num_points)
			matrices.length = num_points;
		var identity = mat4.create();
		var temp = vec3.create();
		var zero = vec3.create();
		var UP = vec3.fromValues(0,1,0);
		var FRONT = vec3.fromValues(0,0,-1);
		var RIGHT = vec3.fromValues(1,0,0);
		var R = quat.create();

		var front = vec3.create();
		var right = vec3.create();
		var top = vec3.create();

		for(var i = 0; i < vertices.length; i += 3)
		{
			var index = i/3;
			var m = matrices[index];
			if(!m)
				m = matrices[index] = mat4.create();
			m.set( identity );
			var point = vertices.subarray(i,i+3);

			switch(this.properties.mode)
			{
				case LGraphPointsToInstances.NORMAL: 
					mat4.setTranslation( m, point );
					if(normals)
					{
						var normal = normals.subarray(i,i+3);
						top.set( normal );
						vec3.normalize( top, top );
						vec3.cross( right, FRONT, top );
						vec3.normalize( right, right );
						vec3.cross( front, right, top );
						vec3.normalize( front, front );
						m.set(right,0);
						m.set(top,4);
						m.set(front,8);
						mat4.setTranslation( m, point );
					}
					break;
				case LGraphPointsToInstances.VERTICAL: 
					mat4.setTranslation( m, point );
					break;
				case LGraphPointsToInstances.SPHERICAL: 
					front.set( point );
					vec3.normalize( front, front );
					vec3.cross( right, UP, front );
					vec3.normalize( right, right );
					vec3.cross( top, front, right );
					vec3.normalize( top, top );
					m.set(right,0);
					m.set(top,4);
					m.set(front,8);
					mat4.setTranslation( m, point );
					break;
				case LGraphPointsToInstances.RANDOM:
					temp[0] = Math.random()*2 - 1;
					temp[1] = Math.random()*2 - 1;
					temp[2] = Math.random()*2 - 1;
					vec3.normalize( temp, temp );
					quat.setAxisAngle( R, temp, Math.random() * 2 * Math.PI );
					mat4.fromQuat(m, R);
					mat4.setTranslation( m, point );
					break;
				case LGraphPointsToInstances.RANDOM_VERTICAL:
					quat.setAxisAngle( R, UP, Math.random() * 2 * Math.PI );
					mat4.fromQuat(m, R);
					mat4.setTranslation( m, point );
					break;
			}
		}

		this._version = geometry._version;
		this._geometry_id = geometry._id;
	}

	LiteGraph.registerNodeType( "geometry/points_to_instances", LGraphPointsToInstances );


	function LGraphGeometryTransform() {
		this.addInput("in", "geometry,[mat4]");
		this.addInput("mat4", "mat4");
		this.addOutput("out", "geometry");
		this.properties = {};

		this.geometry = {
			type: "triangles",
			vertices: null,
			_id: generateGeometryId(),
			_version: 0
		};

		this._last_geometry_id = -1;
		this._last_version = -1;
		this._last_key = "";

		this.must_update = true;
	}

	LGraphGeometryTransform.title = "Transform";

	LGraphGeometryTransform.prototype.onExecute = function() {

		var input = this.getInputData(0);
		var model = this.getInputData(1);

		if(!input)
			return;

		//array of matrices
		if(input.constructor === Array)
		{
			if(input.length == 0)
				return;
			this.outputs[0].type = "[mat4]";
			if( !this.isOutputConnected(0) )
				return;

			if(!model)
			{
				this.setOutputData(0,input);
				return;
			}

			if(!this._output)
				this._output = new Array();
			if(this._output.length != input.length)
				this._output.length = input.length;
			for(var i = 0; i < input.length; ++i)
			{
				var m = this._output[i];
				if(!m)
					m = this._output[i] = mat4.create();
				mat4.multiply(m,input[i],model);
			}
			this.setOutputData(0,this._output);
			return;
		}

		//geometry
		if(!input.vertices || !input.vertices.length)
			return;
		var geo = input;
		this.outputs[0].type = "geometry";
		if( !this.isOutputConnected(0) )
			return;
		if(!model)
		{
			this.setOutputData(0,geo);
			return;
		}

		var key = typedArrayToArray(model).join(",");

		if( this.must_update || geo._id != this._last_geometry_id || geo._version != this._last_version || key != this._last_key )
		{
			this.updateGeometry(geo, model);
			this._last_key = key;
			this._last_version = geo._version;
			this._last_geometry_id = geo._id;
			this.must_update = false;
		}

		this.setOutputData(0,this.geometry);
	}

	LGraphGeometryTransform.prototype.updateGeometry = function(geometry, model) {
		var old_vertices = geometry.vertices;
		var vertices = this.geometry.vertices;
		if( !vertices || vertices.length != old_vertices.length )
			vertices = this.geometry.vertices = new Float32Array( old_vertices.length );
		var temp = vec3.create();

		for(var i = 0, l = vertices.length; i < l; i+=3)
		{
			temp[0] = old_vertices[i]; temp[1] = old_vertices[i+1]; temp[2] = old_vertices[i+2]; 
			mat4.multiplyVec3( temp, model, temp );
			vertices[i] = temp[0]; vertices[i+1] = temp[1]; vertices[i+2] = temp[2];
		}

		if(geometry.normals)
		{
			if( !this.geometry.normals || this.geometry.normals.length != geometry.normals.length )
				this.geometry.normals = new Float32Array( geometry.normals.length );
			var normals = this.geometry.normals;
			var normal_model = mat4.invert(mat4.create(), model);
			if(normal_model)
				mat4.transpose(normal_model, normal_model);
			var old_normals = geometry.normals;
			for(var i = 0, l = normals.length; i < l; i+=3)
			{
				temp[0] = old_normals[i]; temp[1] = old_normals[i+1]; temp[2] = old_normals[i+2]; 
				mat4.multiplyVec3( temp, normal_model, temp );
				normals[i] = temp[0]; normals[i+1] = temp[1]; normals[i+2] = temp[2];
			}
		}

		this.geometry.type = geometry.type;
		this.geometry._version++;
	}

	LiteGraph.registerNodeType( "geometry/transform", LGraphGeometryTransform );


	function LGraphGeometryPolygon() {
		this.addInput("sides", "number");
		this.addInput("radius", "number");
		this.addOutput("out", "geometry");
		this.properties = { sides: 6, radius: 1, uvs: false }

		this.geometry = {
			type: "line_loop",
			vertices: null,
			_id: generateGeometryId()
		};
		this.geometry_id = -1;
		this.version = -1;
		this.must_update = true;

		this.last_info = { sides: -1, radius: -1 };
	}

	LGraphGeometryPolygon.title = "Polygon";

	LGraphGeometryPolygon.prototype.onExecute = function() {

		if( !this.isOutputConnected(0) )
			return;

		var sides = this.getInputOrProperty("sides");
		var radius = this.getInputOrProperty("radius");
		sides = Math.max(3,sides)|0;

		//update
		if( this.last_info.sides != sides || this.last_info.radius != radius )
			this.updateGeometry(sides, radius);

		this.setOutputData(0,this.geometry);
	}

	LGraphGeometryPolygon.prototype.updateGeometry = function(sides, radius) {
		var num = 3*sides;
		var vertices = this.geometry.vertices;
		if( !vertices || vertices.length != num )
			vertices = this.geometry.vertices = new Float32Array( 3*sides );
		var delta = (Math.PI * 2) / sides;
		var gen_uvs = this.properties.uvs;
		if(gen_uvs)
		{
			uvs = this.geometry.coords = new Float32Array( 3*sides );
		}


		for(var i = 0; i < sides; ++i)
		{
			var angle = delta * -i;
			var x = Math.cos( angle ) * radius;
			var y = 0;
			var z = Math.sin( angle ) * radius;
			vertices[i*3] = x;
			vertices[i*3+1] = y;
			vertices[i*3+2] = z;

			if(gen_uvs)
			{
				

			}
		}
		this.geometry._id = ++this.geometry_id;
		this.geometry._version = ++this.version;
		this.last_info.sides = sides;
		this.last_info.radius = radius;
	}

	LiteGraph.registerNodeType( "geometry/polygon", LGraphGeometryPolygon );


	function LGraphGeometryExtrude() {

		this.addInput("", "geometry");
		this.addOutput("", "geometry");
		this.properties = { top_cap: true, bottom_cap: true, offset: [0,100,0] };
		this.version = -1;

		this._last_geo_version = -1;
		this._must_update = true;
	}

	LGraphGeometryExtrude.title = "extrude";

	LGraphGeometryExtrude.prototype.onPropertyChanged = function(name, value)
	{
		this._must_update = true;
	}

	LGraphGeometryExtrude.prototype.onExecute = function()
	{
		var geo = this.getInputData(0);
		if( !geo || !this.isOutputConnected(0) )
			return;

		if(geo.version != this._last_geo_version || this._must_update)
		{
			this._geo = this.extrudeGeometry( geo, this._geo );
			if(this._geo)
				this._geo.version = this.version++;
			this._must_update = false;
		}

		this.setOutputData(0, this._geo);
	}

	LGraphGeometryExtrude.prototype.extrudeGeometry = function( geo )
	{
		//for every pair of vertices
		var vertices = geo.vertices;
		var num_points = vertices.length / 3;

		var tempA = vec3.create();
		var tempB = vec3.create();
		var tempC = vec3.create();
		var tempD = vec3.create();
		var offset = new Float32Array( this.properties.offset );

		if(geo.type == "line_loop")
		{
			var new_vertices = new Float32Array( num_points * 6 * 3 ); //every points become 6 ( caps not included )
			var npos = 0;
			for(var i = 0, l = vertices.length; i < l; i += 3)
			{
				tempA[0] = vertices[i]; tempA[1] = vertices[i+1]; tempA[2] = vertices[i+2];

				if( i+3 < l ) //loop
				{
					tempB[0] = vertices[i+3]; tempB[1] = vertices[i+4]; tempB[2] = vertices[i+5];
				}
				else
				{
					tempB[0] = vertices[0]; tempB[1] = vertices[1]; tempB[2] = vertices[2];
				}

				vec3.add( tempC, tempA, offset );
				vec3.add( tempD, tempB, offset );

				new_vertices.set( tempA, npos ); npos += 3;
				new_vertices.set( tempB, npos ); npos += 3;
				new_vertices.set( tempC, npos ); npos += 3;

				new_vertices.set( tempB, npos ); npos += 3;
				new_vertices.set( tempD, npos ); npos += 3;
				new_vertices.set( tempC, npos ); npos += 3;
			}
		}

		var out_geo = {
			_id: generateGeometryId(),
			type: "triangles",
			vertices: new_vertices
		};

		return out_geo;
	}

	LiteGraph.registerNodeType( "geometry/extrude", LGraphGeometryExtrude );


	function LGraphGeometryEval() {
		this.addInput("in", "geometry");
		this.addOutput("out", "geometry");

		this.properties = {
			code: "V[1] += 0.01 * Math.sin(I + T*0.001);",
			execute_every_frame: false
		};

		this.geometry = null;
		this.geometry_id = -1;
		this.version = -1;
		this.must_update = true;

		this.vertices = null;
		this.func = null;
	}

	LGraphGeometryEval.title = "geoeval";
	LGraphGeometryEval.desc = "eval code";

	LGraphGeometryEval.widgets_info = {
		code: { widget: "code" }
	};

	LGraphGeometryEval.prototype.onConfigure = function(o)
	{
		this.compileCode();
	}

	LGraphGeometryEval.prototype.compileCode = function()
	{
		if(!this.properties.code)
			return;

		try
		{
			this.func = new Function("V","I","T", this.properties.code); 
			this.boxcolor = "#AFA";
			this.must_update = true;
		}
		catch (err)
		{
			this.boxcolor = "red";
		}
	}

	LGraphGeometryEval.prototype.onPropertyChanged = function(name, value)
	{
		if(name == "code")
		{
			this.properties.code = value;
			this.compileCode();
		}
	}

	LGraphGeometryEval.prototype.onExecute = function() {
		var geometry = this.getInputData(0);
		if(!geometry)
			return;

		if(!this.func)
		{
			this.setOutputData(0,geometry);
			return;
		}

		if( this.geometry_id != geometry._id || this.version != geometry._version || this.must_update || this.properties.execute_every_frame )
		{
			this.must_update = false;
			this.geometry_id = geometry._id;
			if(this.properties.execute_every_frame)
				this.version++;
			else
				this.version = geometry._version;
			var func = this.func;
			var T = getTime();

			//clone
			if(!this.geometry)
				this.geometry = {};
			for(var i in geometry)
			{
				if(geometry[i] == null)
					continue;
				if( geometry[i].constructor == Float32Array )
					this.geometry[i] = new Float32Array( geometry[i] );
				else
					this.geometry[i] = geometry[i];
			}
			this.geometry._id = geometry._id;
			if(this.properties.execute_every_frame)
				this.geometry._version = this.version;
			else
				this.geometry._version = geometry._version + 1;

			var V = vec3.create();
			var vertices = this.vertices;
			if(!vertices || this.vertices.length != geometry.vertices.length)
				vertices = this.vertices = new Float32Array( geometry.vertices );
			else
				vertices.set( geometry.vertices );
			for(var i = 0; i < vertices.length; i+=3)
			{
				V[0] = vertices[i];
				V[1] = vertices[i+1];
				V[2] = vertices[i+2];
				func(V,i/3,T);
				vertices[i] = V[0];
				vertices[i+1] = V[1];
				vertices[i+2] = V[2];
			}
			this.geometry.vertices = vertices;
		}

		this.setOutputData(0,this.geometry);
	}

	LiteGraph.registerNodeType( "geometry/eval", LGraphGeometryEval );

/*
function LGraphGeometryDisplace() {
		this.addInput("in", "geometry");
		this.addInput("img", "image");
		this.addOutput("out", "geometry");

		this.properties = {
			grid_size: 1
		};

		this.geometry = null;
		this.geometry_id = -1;
		this.version = -1;
		this.must_update = true;

		this.vertices = null;
	}

	LGraphGeometryDisplace.title = "displace";
	LGraphGeometryDisplace.desc = "displace points";

	LGraphGeometryDisplace.prototype.onExecute = function() {
		var geometry = this.getInputData(0);
		var image = this.getInputData(1);
		if(!geometry)
			return;

		if(!image)
		{
			this.setOutputData(0,geometry);
			return;
		}

		if( this.geometry_id != geometry._id || this.version != geometry._version || this.must_update )
		{
			this.must_update = false;
			this.geometry_id = geometry._id;
			this.version = geometry._version;

			//copy
			this.geometry = {};
			for(var i in geometry)
				this.geometry[i] = geometry[i];
			this.geometry._id = geometry._id;
			this.geometry._version = geometry._version + 1;

			var grid_size = this.properties.grid_size;
			if(grid_size != 0)
			{
				var vertices = this.vertices;
				if(!vertices || this.vertices.length != this.geometry.vertices.length)
					vertices = this.vertices = new Float32Array( this.geometry.vertices );
				for(var i = 0; i < vertices.length; i+=3)
				{
					vertices[i] = Math.round(vertices[i]/grid_size) * grid_size;
					vertices[i+1] = Math.round(vertices[i+1]/grid_size) * grid_size;
					vertices[i+2] = Math.round(vertices[i+2]/grid_size) * grid_size;
				}
				this.geometry.vertices = vertices;
			}
		}

		this.setOutputData(0,this.geometry);
	}

	LiteGraph.registerNodeType( "geometry/displace", LGraphGeometryDisplace );
*/

	function LGraphConnectPoints() {
		this.addInput("in", "geometry");
		this.addOutput("out", "geometry");

		this.properties = {
			min_dist: 0.4,
			max_dist: 0.5,
			max_connections: 0,
			probability: 1
		};

		this.geometry_id = -1;
		this.version = -1;
		this.my_version = 1;
		this.must_update = true;
	}

	LGraphConnectPoints.title = "connect points";
	LGraphConnectPoints.desc = "adds indices between near points";

	LGraphConnectPoints.prototype.onPropertyChanged = function(name,value)
	{
		this.must_update = true;
	}

	LGraphConnectPoints.prototype.onExecute = function() {
		var geometry = this.getInputData(0);
		if(!geometry)
			return;

		if( this.geometry_id != geometry._id || this.version != geometry._version || this.must_update )
		{
			this.must_update = false;
			this.geometry_id = geometry._id;
			this.version = geometry._version;

			//copy
			this.geometry = {};
			for(var i in geometry)
				this.geometry[i] = geometry[i];
			this.geometry._id = generateGeometryId();
			this.geometry._version = this.my_version++;

			var vertices = geometry.vertices;
			var l = vertices.length;
			var min_dist = this.properties.min_dist;
			var max_dist = this.properties.max_dist;
			var probability = this.properties.probability;
			var max_connections = this.properties.max_connections;
			var indices = [];
			
			for(var i = 0; i < l; i+=3)
			{
				var x = vertices[i];
				var y = vertices[i+1];
				var z = vertices[i+2];
				var connections = 0;
				for(var j = i+3; j < l; j+=3)
				{
					var x2 = vertices[j];
					var y2 = vertices[j+1];
					var z2 = vertices[j+2];
					var dist = Math.sqrt( (x-x2)*(x-x2) + (y-y2)*(y-y2) + (z-z2)*(z-z2));
					if(dist > max_dist || dist < min_dist || (probability < 1 && probability < Math.random()) )
						continue;
					indices.push(i/3,j/3);
					connections += 1;
					if(max_connections && connections > max_connections)
						break;
				}
			}
			this.geometry.indices = this.indices = new Uint32Array(indices);
		}

		if(this.indices && this.indices.length)
		{
			this.geometry.indices = this.indices;
			this.setOutputData( 0, this.geometry );
		}
		else
			this.setOutputData( 0, null );
	}

	LiteGraph.registerNodeType( "geometry/connectPoints", LGraphConnectPoints );

    //Works with Litegl.js to create WebGL nodes
    if (typeof GL == "undefined") //LiteGL RELATED **********************************************
		return;

	function LGraphToGeometry() {
		this.addInput("mesh", "mesh");
		this.addOutput("out", "geometry");

		this.geometry = {};
		this.last_mesh = null;
	}

	LGraphToGeometry.title = "to geometry";
	LGraphToGeometry.desc = "converts a mesh to geometry";

	LGraphToGeometry.prototype.onExecute = function() {
		var mesh = this.getInputData(0);
		if(!mesh)
			return;

		if(mesh != this.last_mesh)
		{
			this.last_mesh = mesh;
			for(i in mesh.vertexBuffers)
			{
				var buffer = mesh.vertexBuffers[i];
				this.geometry[i] = buffer.data
			}
			if(mesh.indexBuffers["triangles"])
				this.geometry.indices = mesh.indexBuffers["triangles"].data;

			this.geometry._id = generateGeometryId();
			this.geometry._version = 0;
		}

		this.setOutputData(0,this.geometry);
		if(this.geometry)
			this.setOutputData(1,this.geometry.vertices);
	}

	LiteGraph.registerNodeType( "geometry/toGeometry", LGraphToGeometry );

	function LGraphGeometryToMesh() {
		this.addInput("in", "geometry");
		this.addOutput("mesh", "mesh");
		this.properties = {};
		this.version = -1;
		this.mesh = null;
	}

	LGraphGeometryToMesh.title = "Geo to Mesh";

	LGraphGeometryToMesh.prototype.updateMesh = function(geometry)
	{
		if(!this.mesh)
			this.mesh = new GL.Mesh();

		for(var i in geometry)
		{
			if(i[0] == "_")
				continue;

			var buffer_data = geometry[i];

			var info = GL.Mesh.common_buffers[i];
			if(!info && i != "indices") //unknown buffer
				continue;
			var spacing = info ? info.spacing : 3;
			var mesh_buffer = this.mesh.vertexBuffers[i];

			if(!mesh_buffer || mesh_buffer.data.length != buffer_data.length)
			{
				mesh_buffer = new GL.Buffer( i == "indices" ? GL.ELEMENT_ARRAY_BUFFER : GL.ARRAY_BUFFER, buffer_data, spacing, GL.DYNAMIC_DRAW );
			}
			else
			{
				mesh_buffer.data.set( buffer_data );
				mesh_buffer.upload(GL.DYNAMIC_DRAW);
			}

			this.mesh.addBuffer( i, mesh_buffer );
		}

		if(this.mesh.vertexBuffers.normals &&this.mesh.vertexBuffers.normals.data.length != this.mesh.vertexBuffers.vertices.data.length )
		{
			var n = new Float32Array([0,1,0]);
			var normals = new Float32Array( this.mesh.vertexBuffers.vertices.data.length );
			for(var i = 0; i < normals.length; i+= 3)
				normals.set( n, i );
			mesh_buffer = new GL.Buffer( GL.ARRAY_BUFFER, normals, 3 );
			this.mesh.addBuffer( "normals", mesh_buffer );
		}

		this.mesh.updateBoundingBox();
		this.geometry_id = this.mesh.id = geometry._id;
		this.version = this.mesh.version = geometry._version;
		return this.mesh;
	}

	LGraphGeometryToMesh.prototype.onExecute = function() {

		var geometry = this.getInputData(0);
		if(!geometry)
			return;
		if( this.version != geometry._version || this.geometry_id != geometry._id )
			this.updateMesh( geometry );
		this.setOutputData(0, this.mesh);
	}

	LiteGraph.registerNodeType( "geometry/toMesh", LGraphGeometryToMesh );

	function LGraphRenderMesh() {
		this.addInput("mesh", "mesh");
		this.addInput("mat4", "mat4");
		this.addInput("tex", "texture");

		this.properties = {
			enabled: true,
			primitive: GL.TRIANGLES,
			additive: false,
			color: [1,1,1],
			opacity: 1
		};

		this.color = vec4.create([1,1,1,1]);
		this.model_matrix = mat4.create();
		this.uniforms = {
			u_color: this.color,
			u_model: this.model_matrix
		};
	}

	LGraphRenderMesh.title = "Render Mesh";
	LGraphRenderMesh.desc = "renders a mesh flat";

	LGraphRenderMesh.PRIMITIVE_VALUES = { "points":GL.POINTS, "lines":GL.LINES, "line_loop":GL.LINE_LOOP,"line_strip":GL.LINE_STRIP, "triangles":GL.TRIANGLES, "triangle_fan":GL.TRIANGLE_FAN, "triangle_strip":GL.TRIANGLE_STRIP };

	LGraphRenderMesh.widgets_info = {
		primitive: { widget: "combo", values: LGraphRenderMesh.PRIMITIVE_VALUES },
		color: { widget: "color" }
	};

	LGraphRenderMesh.prototype.onExecute = function() {

		if(!this.properties.enabled)
			return;

		var mesh = this.getInputData(0);
		if(!mesh)
			return;

		if(!LiteGraph.LGraphRender.onRequestCameraMatrices)
		{
			console.warn("cannot render geometry, LiteGraph.onRequestCameraMatrices is null, remember to fill this with a callback(view_matrix, projection_matrix,viewprojection_matrix) to use 3D rendering from the graph");
			return;
		}

		LiteGraph.LGraphRender.onRequestCameraMatrices( view_matrix, projection_matrix,viewprojection_matrix );
		var shader = null;
		var texture = this.getInputData(2);
		if(texture)
		{
			shader = gl.shaders["textured"];
			if(!shader)
				shader = gl.shaders["textured"] = new GL.Shader( LGraphRenderPoints.vertex_shader_code, LGraphRenderPoints.fragment_shader_code, { USE_TEXTURE:"" });
		}
		else
		{
			shader = gl.shaders["flat"];
			if(!shader)
				shader = gl.shaders["flat"] = new GL.Shader( LGraphRenderPoints.vertex_shader_code, LGraphRenderPoints.fragment_shader_code );
		}

		this.color.set( this.properties.color );
		this.color[3] = this.properties.opacity;

		var model_matrix = this.model_matrix;
		var m = this.getInputData(1);
		if(m)
			model_matrix.set(m);
		else
			mat4.identity( model_matrix );

		this.uniforms.u_point_size = 1;
		var primitive = this.properties.primitive;

		shader.uniforms( global_uniforms );
		shader.uniforms( this.uniforms );

		if(this.properties.opacity >= 1)
			gl.disable( gl.BLEND );
		else
			gl.enable( gl.BLEND );
		gl.enable( gl.DEPTH_TEST );
		if( this.properties.additive )
		{
			gl.blendFunc( gl.SRC_ALPHA, gl.ONE );
			gl.depthMask( false );
		}
		else
			gl.blendFunc( gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA );

		var indices = "indices";
		if( mesh.indexBuffers.triangles )
			indices = "triangles";
		shader.draw( mesh, primitive, indices );
		gl.disable( gl.BLEND );
		gl.depthMask( true );
	}

	LiteGraph.registerNodeType( "geometry/render_mesh", LGraphRenderMesh );

	//**************************


	function LGraphGeometryPrimitive() {
		this.addInput("size", "number");
		this.addOutput("out", "mesh");
		this.properties = { type: 1, size: 1, subdivisions: 32 };

		this.version = (Math.random() * 100000)|0;
		this.last_info = { type: -1, size: -1, subdivisions: -1 };
	}

	LGraphGeometryPrimitive.title = "Primitive";

	LGraphGeometryPrimitive.VALID = { "CUBE":1, "PLANE":2, "CYLINDER":3, "SPHERE":4, "CIRCLE":5, "HEMISPHERE":6, "ICOSAHEDRON":7, "CONE":8, "QUAD":9 };
	LGraphGeometryPrimitive.widgets_info = {
		type: { widget: "combo", values: LGraphGeometryPrimitive.VALID }
	};

	LGraphGeometryPrimitive.prototype.onExecute = function() {

		if( !this.isOutputConnected(0) )
			return;

		var size = this.getInputOrProperty("size");

		//update
		if( this.last_info.type != this.properties.type || this.last_info.size != size || this.last_info.subdivisions != this.properties.subdivisions )
			this.updateMesh( this.properties.type, size, this.properties.subdivisions );

		this.setOutputData(0,this._mesh);
	}

	LGraphGeometryPrimitive.prototype.updateMesh = function(type, size, subdivisions)
	{
		subdivisions = Math.max(0,subdivisions)|0;

		switch (type)
		{
			case 1: //CUBE: 
				this._mesh = GL.Mesh.cube({size: size, normals:true,coords:true});
				break;
			case 2: //PLANE:
				this._mesh = GL.Mesh.plane({size: size, xz: true, detail: subdivisions, normals:true,coords:true});
				break;
			case 3: //CYLINDER:
				this._mesh = GL.Mesh.cylinder({size: size, subdivisions: subdivisions, normals:true,coords:true});
				break;
			case 4: //SPHERE:
				this._mesh = GL.Mesh.sphere({size: size, "long": subdivisions, lat: subdivisions, normals:true,coords:true});
				break;
			case 5: //CIRCLE:
				this._mesh = GL.Mesh.circle({size: size, slices: subdivisions, normals:true, coords:true});
				break;
			case 6: //HEMISPHERE:
				this._mesh = GL.Mesh.sphere({size: size, "long": subdivisions, lat: subdivisions, normals:true, coords:true, hemi: true});
				break;
			case 7: //ICOSAHEDRON:
				this._mesh = GL.Mesh.icosahedron({size: size, subdivisions:subdivisions });
				break;
			case 8: //CONE:
				this._mesh = GL.Mesh.cone({radius: size, height: size, subdivisions:subdivisions });
				break;
			case 9: //QUAD:
				this._mesh = GL.Mesh.plane({size: size, xz: false, detail: subdivisions, normals:true, coords:true });
				break;
		}

		this.last_info.type = type;
		this.last_info.size = size;
		this.last_info.subdivisions = subdivisions;
		this._mesh.version = this.version++;
	}

	LiteGraph.registerNodeType( "geometry/mesh_primitive", LGraphGeometryPrimitive );


	function LGraphRenderPoints() {
		this.addInput("in", "geometry");
		this.addInput("mat4", "mat4");
		this.addInput("tex", "texture");
		this.properties = {
			enabled: true,
			point_size: 0.1,
			fixed_size: false,
			additive: true,
			color: [1,1,1],
			opacity: 1
		};

		this.color = vec4.create([1,1,1,1]);

		this.uniforms = {
			u_point_size: 1,
			u_perspective: 1,
			u_point_perspective: 1,
			u_color: this.color
		};

		this.geometry_id = -1;
		this.version = -1;
		this.mesh = null;
	}

	LGraphRenderPoints.title = "renderPoints";
	LGraphRenderPoints.desc = "render points with a texture";

	LGraphRenderPoints.widgets_info = {
		color: { widget: "color" }
	};

	LGraphRenderPoints.prototype.updateMesh = function(geometry)
	{
		var buffer = this.buffer;
		if(!this.buffer || !this.buffer.data || this.buffer.data.length != geometry.vertices.length)
			this.buffer = new GL.Buffer( GL.ARRAY_BUFFER, geometry.vertices,3,GL.DYNAMIC_DRAW);
		else
		{
			this.buffer.data.set( geometry.vertices );
			this.buffer.upload(GL.DYNAMIC_DRAW);
		}

		if(!this.mesh)
			this.mesh = new GL.Mesh();

		this.mesh.addBuffer("vertices",this.buffer);
		this.geometry_id = this.mesh.id = geometry._id;
		this.version = this.mesh.version = geometry._version;
	}

	LGraphRenderPoints.prototype.onExecute = function() {

		if(!this.properties.enabled)
			return;

		var geometry = this.getInputData(0);
		if(!geometry)
			return;
		if(this.version != geometry._version || this.geometry_id != geometry._id )
			this.updateMesh( geometry );

		if(!LiteGraph.LGraphRender.onRequestCameraMatrices)
		{
			console.warn("cannot render geometry, LiteGraph.onRequestCameraMatrices is null, remember to fill this with a callback(view_matrix, projection_matrix,viewprojection_matrix) to use 3D rendering from the graph");
			return;
		}

		LiteGraph.LGraphRender.onRequestCameraMatrices( view_matrix, projection_matrix,viewprojection_matrix );
		var shader = null;

		var texture = this.getInputData(2);
		
		if(texture)
		{
			shader = gl.shaders["textured_points"];
			if(!shader)
				shader = gl.shaders["textured_points"] = new GL.Shader( LGraphRenderPoints.vertex_shader_code, LGraphRenderPoints.fragment_shader_code, { USE_TEXTURED_POINTS:"" });
		}
		else
		{
			shader = gl.shaders["points"];
			if(!shader)
				shader = gl.shaders["points"] = new GL.Shader( LGraphRenderPoints.vertex_shader_code, LGraphRenderPoints.fragment_shader_code, { USE_POINTS: "" });
		}

		this.color.set( this.properties.color );
		this.color[3] = this.properties.opacity;

		var m = this.getInputData(1);
		if(m)
			model_matrix.set(m);
		else
			mat4.identity( model_matrix );

		this.uniforms.u_point_size = this.properties.point_size;
		this.uniforms.u_point_perspective = this.properties.fixed_size ? 0 : 1;
		this.uniforms.u_perspective = gl.viewport_data[3] * projection_matrix[5];

		shader.uniforms( global_uniforms );
		shader.uniforms( this.uniforms );

		if(this.properties.opacity >= 1)
			gl.disable( gl.BLEND );
		else
			gl.enable( gl.BLEND );

		gl.enable( gl.DEPTH_TEST );
		if( this.properties.additive )
		{
			gl.blendFunc( gl.SRC_ALPHA, gl.ONE );
			gl.depthMask( false );
		}
		else
			gl.blendFunc( gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA );

		shader.draw( this.mesh, GL.POINTS );

		gl.disable( gl.BLEND );
		gl.depthMask( true );
	}

	LiteGraph.registerNodeType( "geometry/render_points", LGraphRenderPoints );

	LGraphRenderPoints.vertex_shader_code = '\
		precision mediump float;\n\
		attribute vec3 a_vertex;\n\
		varying vec3 v_vertex;\n\
		attribute vec3 a_normal;\n\
		varying vec3 v_normal;\n\
		#ifdef USE_COLOR\n\
			attribute vec4 a_color;\n\
			varying vec4 v_color;\n\
		#endif\n\
		attribute vec2 a_coord;\n\
		varying vec2 v_coord;\n\
		#ifdef USE_SIZE\n\
			attribute float a_extra;\n\
		#endif\n\
		#ifdef USE_INSTANCING\n\
			attribute mat4 u_model;\n\
		#else\n\
			uniform mat4 u_model;\n\
		#endif\n\
		uniform mat4 u_viewprojection;\n\
		uniform float u_point_size;\n\
		uniform float u_perspective;\n\
		uniform float u_point_perspective;\n\
		float computePointSize(float radius, float w)\n\
		{\n\
			if(radius < 0.0)\n\
				return -radius;\n\
			return u_perspective * radius / w;\n\
		}\n\
		void main() {\n\
			v_coord = a_coord;\n\
			#ifdef USE_COLOR\n\
				v_color = a_color;\n\
			#endif\n\
			v_vertex = ( u_model * vec4( a_vertex, 1.0 )).xyz;\n\
			v_normal = ( u_model * vec4( a_normal, 0.0 )).xyz;\n\
			gl_Position = u_viewprojection * vec4(v_vertex,1.0);\n\
			gl_PointSize = u_point_size;\n\
			#ifdef USE_SIZE\n\
				gl_PointSize = a_extra;\n\
			#endif\n\
			if(u_point_perspective != 0.0)\n\
				gl_PointSize = computePointSize( gl_PointSize, gl_Position.w );\n\
		}\
	';

	LGraphRenderPoints.fragment_shader_code = '\
		precision mediump float;\n\
		uniform vec4 u_color;\n\
		#ifdef USE_COLOR\n\
			varying vec4 v_color;\n\
		#endif\n\
		varying vec2 v_coord;\n\
		uniform sampler2D u_texture;\n\
		void main() {\n\
			vec4 color = u_color;\n\
			#ifdef USE_TEXTURED_POINTS\n\
				color *= texture2D(u_texture, gl_PointCoord.xy);\n\
			#else\n\
				#ifdef USE_TEXTURE\n\
				  color *= texture2D(u_texture, v_coord);\n\
				  if(color.a < 0.1)\n\
					discard;\n\
				#endif\n\
				#ifdef USE_POINTS\n\
					float dist = length( gl_PointCoord.xy - vec2(0.5) );\n\
					if( dist > 0.45 )\n\
						discard;\n\
				#endif\n\
			#endif\n\
			#ifdef USE_COLOR\n\
				color *= v_color;\n\
			#endif\n\
			gl_FragColor = color;\n\
		}\
	';

	//based on https://inconvergent.net/2019/depth-of-field/
	/*
	function LGraphRenderGeometryDOF() {
		this.addInput("in", "geometry");
		this.addInput("mat4", "mat4");
		this.addInput("tex", "texture");
		this.properties = {
			enabled: true,
			lines: true,
			point_size: 0.1,
			fixed_size: false,
			additive: true,
			color: [1,1,1],
			opacity: 1
		};

		this.color = vec4.create([1,1,1,1]);

		this.uniforms = {
			u_point_size: 1,
			u_perspective: 1,
			u_point_perspective: 1,
			u_color: this.color
		};

		this.geometry_id = -1;
		this.version = -1;
		this.mesh = null;
	}

	LGraphRenderGeometryDOF.widgets_info = {
		color: { widget: "color" }
	};

	LGraphRenderGeometryDOF.prototype.updateMesh = function(geometry)
	{
		var buffer = this.buffer;
		if(!this.buffer || this.buffer.data.length != geometry.vertices.length)
			this.buffer = new GL.Buffer( GL.ARRAY_BUFFER, geometry.vertices,3,GL.DYNAMIC_DRAW);
		else
		{
			this.buffer.data.set( geometry.vertices );
			this.buffer.upload(GL.DYNAMIC_DRAW);
		}

		if(!this.mesh)
			this.mesh = new GL.Mesh();

		this.mesh.addBuffer("vertices",this.buffer);
		this.geometry_id = this.mesh.id = geometry._id;
		this.version = this.mesh.version = geometry._version;
	}

	LGraphRenderGeometryDOF.prototype.onExecute = function() {

		if(!this.properties.enabled)
			return;

		var geometry = this.getInputData(0);
		if(!geometry)
			return;
		if(this.version != geometry._version || this.geometry_id != geometry._id )
			this.updateMesh( geometry );

		if(!LiteGraph.LGraphRender.onRequestCameraMatrices)
		{
			console.warn("cannot render geometry, LiteGraph.onRequestCameraMatrices is null, remember to fill this with a callback(view_matrix, projection_matrix,viewprojection_matrix) to use 3D rendering from the graph");
			return;
		}

		LiteGraph.LGraphRender.onRequestCameraMatrices( view_matrix, projection_matrix,viewprojection_matrix );
		var shader = null;

		var texture = this.getInputData(2);
		
		if(texture)
		{
			shader = gl.shaders["textured_points"];
			if(!shader)
				shader = gl.shaders["textured_points"] = new GL.Shader( LGraphRenderGeometryDOF.vertex_shader_code, LGraphRenderGeometryDOF.fragment_shader_code, { USE_TEXTURED_POINTS:"" });
		}
		else
		{
			shader = gl.shaders["points"];
			if(!shader)
				shader = gl.shaders["points"] = new GL.Shader( LGraphRenderGeometryDOF.vertex_shader_code, LGraphRenderGeometryDOF.fragment_shader_code, { USE_POINTS: "" });
		}

		this.color.set( this.properties.color );
		this.color[3] = this.properties.opacity;

		var m = this.getInputData(1);
		if(m)
			model_matrix.set(m);
		else
			mat4.identity( model_matrix );

		this.uniforms.u_point_size = this.properties.point_size;
		this.uniforms.u_point_perspective = this.properties.fixed_size ? 0 : 1;
		this.uniforms.u_perspective = gl.viewport_data[3] * projection_matrix[5];

		shader.uniforms( global_uniforms );
		shader.uniforms( this.uniforms );

		if(this.properties.opacity >= 1)
			gl.disable( gl.BLEND );
		else
			gl.enable( gl.BLEND );

		gl.enable( gl.DEPTH_TEST );
		if( this.properties.additive )
		{
			gl.blendFunc( gl.SRC_ALPHA, gl.ONE );
			gl.depthMask( false );
		}
		else
			gl.blendFunc( gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA );

		shader.draw( this.mesh, GL.POINTS );

		gl.disable( gl.BLEND );
		gl.depthMask( true );
	}

	LiteGraph.registerNodeType( "geometry/render_dof", LGraphRenderGeometryDOF );

	LGraphRenderGeometryDOF.vertex_shader_code = '\
		precision mediump float;\n\
		attribute vec3 a_vertex;\n\
		varying vec3 v_vertex;\n\
		attribute vec3 a_normal;\n\
		varying vec3 v_normal;\n\
		#ifdef USE_COLOR\n\
			attribute vec4 a_color;\n\
			varying vec4 v_color;\n\
		#endif\n\
		attribute vec2 a_coord;\n\
		varying vec2 v_coord;\n\
		#ifdef USE_SIZE\n\
			attribute float a_extra;\n\
		#endif\n\
		#ifdef USE_INSTANCING\n\
			attribute mat4 u_model;\n\
		#else\n\
			uniform mat4 u_model;\n\
		#endif\n\
		uniform mat4 u_viewprojection;\n\
		uniform float u_point_size;\n\
		uniform float u_perspective;\n\
		uniform float u_point_perspective;\n\
		float computePointSize(float radius, float w)\n\
		{\n\
			if(radius < 0.0)\n\
				return -radius;\n\
			return u_perspective * radius / w;\n\
		}\n\
		void main() {\n\
			v_coord = a_coord;\n\
			#ifdef USE_COLOR\n\
				v_color = a_color;\n\
			#endif\n\
			v_vertex = ( u_model * vec4( a_vertex, 1.0 )).xyz;\n\
			v_normal = ( u_model * vec4( a_normal, 0.0 )).xyz;\n\
			gl_Position = u_viewprojection * vec4(v_vertex,1.0);\n\
			gl_PointSize = u_point_size;\n\
			#ifdef USE_SIZE\n\
				gl_PointSize = a_extra;\n\
			#endif\n\
			if(u_point_perspective != 0.0)\n\
				gl_PointSize = computePointSize( gl_PointSize, gl_Position.w );\n\
		}\
	';

	LGraphRenderGeometryDOF.fragment_shader_code = '\
		precision mediump float;\n\
		uniform vec4 u_color;\n\
		#ifdef USE_COLOR\n\
			varying vec4 v_color;\n\
		#endif\n\
		varying vec2 v_coord;\n\
		uniform sampler2D u_texture;\n\
		void main() {\n\
			vec4 color = u_color;\n\
			#ifdef USE_TEXTURED_POINTS\n\
				color *= texture2D(u_texture, gl_PointCoord.xy);\n\
			#else\n\
				#ifdef USE_TEXTURE\n\
				  color *= texture2D(u_texture, v_coord);\n\
				  if(color.a < 0.1)\n\
					discard;\n\
				#endif\n\
				#ifdef USE_POINTS\n\
					float dist = length( gl_PointCoord.xy - vec2(0.5) );\n\
					if( dist > 0.45 )\n\
						discard;\n\
				#endif\n\
			#endif\n\
			#ifdef USE_COLOR\n\
				color *= v_color;\n\
			#endif\n\
			gl_FragColor = color;\n\
		}\
	';
	*/



})(this);